package com.yanyeori.framework.codegenerator.generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.yanyeori.framework.core.util.StringUtil;

import java.util.*;

/**
 * 代码生成工具
 * <p>
 * 必需选项：
 * Objects.requireNonNull(dbUrl, "please set dbUrl");
 * Objects.requireNonNull(dbUsername, "please set dbUsername");
 * Objects.requireNonNull(dbPassword, "please set dbPassword");
 * Objects.requireNonNull(tableInclude, "please set tableInclude");
 * Objects.requireNonNull(packageParent, "please set packageParent");
 *
 * @author chenkuan 2021/6/22
 */
public class GenerateCode {

    //生成module与当前module的相对路径
    private String modulePath = "";

    //文件重写
    private boolean fileOverride = true;
    //代码作者
    private String author = "sys";
    //XML ResultMap
    private boolean baseResultMap = true;
    //XML ColumnList
    private boolean baseColumnList = true;
    //XML 二级缓存
    private boolean enableCache = false;
    //实体 Swagger2 注解
    private boolean swagger2 = false;
    //日期转换策略
    private DateType dateType = DateType.ONLY_DATE;

    //controller 后缀
    private String controllerSuffix = "Controller";
    //service 后缀
    private String serviceSuffix = "Service";
    //serviceImpl 后缀
    private String serviceImplSuffix = "ServiceImpl";
    //XML 后缀
    private String xmlSuffix = "Mapper";
    //mapper 后缀
    private String mapperSuffix = "Mapper";

    //数据库类型
    private DbType dbType;
    //数据库驱动
    private String dbDriverName;
    private final String mysqlDriverName = "com.mysql.cj.jdbc.Driver";
    private final String oracleDriverName = "oracle.jdbc.OracleDriver";
    //数据库连接地址
    private String dbUrl;
    //数据库用户名
    private String dbUsername;
    //数据库密码
    private String dbPassword;
    //自定义数据库表字段类型转换
    private ITypeConvert typeConvert;

    //表名前缀
    private String[] tablePrefix;
    //字段名前缀
    private String[] fieldPrefix;
    //表名生成策略
    private NamingStrategy tableNaming = NamingStrategy.underline_to_camel;
    //字段名生成策略
    private NamingStrategy columnNaming = NamingStrategy.underline_to_camel;
    //需要生成的表名
    private String[] tableInclude;
    //排除生成的表名
    private String[] tableExclude;

    //restController风格
    private boolean restControllerStyle = true;
    //自定义 controller 父类
    private String superControllerClass;
    //自定义 service 父类
    private String superServiceClass;
    //自定义 serviceImpl 父类
    private String superServiceImplClass;
    //自定义 entity 父类
    private String superEntityClass;
    //自定义 mapper 父类
    private String superMapperClass;
    //实体是否为链式模式（默认 false）
    private boolean chainModel = false;
    //实体是否使用lomlok注解模式（默认 false）
    private boolean entityLombokModel = false;

    //包路径配置
    private String packageParent; //父包名
    private String packageController = "controller";
    private String packageService = "service";
    private String packageServiceImpl = "service.impl";
    private String packageEntity = "entity";
    private String packageMapper = "mapper";
    private String packageXml = "mapper";//xml在resource下的目录

    //指定是否生成对应文件
    private boolean createController = true;
    private boolean createService = true;
    private boolean createServiceImpl = true;
    private boolean createEntity = true;
    private boolean createMapper = true;
    private boolean createXml = true;

    private GenerateCode() {
    }

    public static GenerateCode builder() {
        return new GenerateCode();
    }

    public void generate() {
        validArgument();

        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String userDir = System.getProperty("user.dir").replace("\\", "/");
        if (StringUtil.isNotEmpty(modulePath)) {
            modulePath = modulePath.replace("\\", "/");
            if (modulePath.length() <= 2 || !Character.isLetter(modulePath.charAt(0)) || !":".equals(String.valueOf(modulePath.charAt(1)))) {
                if (!modulePath.startsWith("/")) {
                    modulePath = "/" + modulePath;
                }
                modulePath = userDir + modulePath;
            }
            if (!modulePath.endsWith("/")) {
                modulePath = modulePath + "/";
            }
        } else {
            modulePath = userDir + "/";
        }
        String projectPath = modulePath;
        gc.setOutputDir(projectPath + "src/main/java");//代码生成存放位置
        gc.setFileOverride(fileOverride);//文件重写
        gc.setBaseResultMap(baseResultMap);//XML ResultMap
        gc.setBaseColumnList(baseColumnList);//XML ColumnList
        gc.setEnableCache(enableCache);//XML 二级缓存
        gc.setOpen(false);//生成后是否打开资源管理器
        gc.setAuthor(author);//代码作者
        gc.setSwagger2(swagger2);//实体 Swagger2 注解
        gc.setDateType(dateType);//日期转换策略
        // 自定义文件命名，注意 %s 会自动填充表实体属性
        gc.setControllerName("%s" + controllerSuffix);
        gc.setServiceName("%s" + serviceSuffix);
        gc.setServiceImplName("%s" + serviceImplSuffix);
        gc.setMapperName("%s" + mapperSuffix);
        gc.setXmlName("%s" + xmlSuffix);
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(dbType);
        if (dbDriverName == null) {
            if (DbType.MYSQL.equals(dbType)) {
                dbDriverName = mysqlDriverName;
            } else if (DbType.ORACLE.equals(dbType)) {
                dbDriverName = oracleDriverName;
            }
        }
        dsc.setDriverName(dbDriverName);
        dsc.setUrl(dbUrl);//数据库连接地址
        dsc.setUsername(dbUsername);//数据库用户名
        dsc.setPassword(dbPassword);//数据库密码
        // 自定义数据库表字段类型转换（可选）
        if (typeConvert != null) {
            ITypeConvert defaultTypeConvert = dsc.getTypeConvert();
            dsc.setTypeConvert((globalConfig, fieldType) -> {
                IColumnType columnType = typeConvert.processTypeConvert(globalConfig, fieldType);
                if (columnType == null) {
                    columnType = defaultTypeConvert.processTypeConvert(globalConfig, fieldType);
                }
                return columnType;
            });
        }
        mpg.setDataSource(dsc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        if (tablePrefix != null) {
            strategy.setTablePrefix(tablePrefix);//表名前缀
        }
        if (fieldPrefix != null) {
            strategy.setFieldPrefix(fieldPrefix);//字段名前缀
        }
        strategy.setNaming(tableNaming);//表名生成策略
        strategy.setColumnNaming(columnNaming);//字段名生成策略
        //strategy.setCapitalMode(NamingStrategy.no_change.equals(columnNaming));
        if (tableInclude != null) {
            strategy.setInclude(tableInclude); // 需要生成的表
        }
        if (tableExclude != null) {
            strategy.setExclude(tableExclude); //排除生成的表
        }
        // RestController风格
        strategy.setRestControllerStyle(restControllerStyle);
        // 自定义 controller 父类
        strategy.setSuperControllerClass(superControllerClass);
        // 自定义 service 父类
        strategy.setSuperServiceClass(superServiceClass);
        // 自定义 serviceImpl 父类
        strategy.setSuperServiceImplClass(superServiceImplClass);
        // 自定义实体父类
        strategy.setSuperEntityClass(superEntityClass);
        // 自定义 mapper 父类
        strategy.setSuperMapperClass(superMapperClass);
        // 实体是否为链式模式（默认 false）
        strategy.setChainModel(chainModel);
        // 实体是否使用lomlok注解模式（默认 false）
        strategy.setEntityLombokModel(entityLombokModel);
        mpg.setStrategy(strategy);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(packageParent);//配置父包名
        pc.setController(packageController);
        pc.setService(packageService);
        pc.setServiceImpl(packageServiceImpl);
        pc.setEntity(packageEntity);
        pc.setMapper(packageMapper);
        pc.setXml(packageXml);
        mpg.setPackageInfo(pc);

        // 自定义模板配置，可以 copy 源码 mybatis-plus/src/main/resources/template 下面内容修改，
        // 放置自己项目的 src/main/resources/template 目录下, 默认名称可以不配置，也可以自定义模板名称
        TemplateConfig tc = new TemplateConfig();
        if (!createController) {
            tc.setController(null);
        }
        if (!createService) {
            tc.setService(null);
        }
        if (!createServiceImpl) {
            tc.setServiceImpl(null);
        }
        if (!createEntity) {
            tc.setEntity(null);
        }
        if (!createMapper) {
            tc.setMapper(null);
        }
        tc.setXml(null);
        mpg.setTemplate(tc);

        //模板引擎
        mpg.setTemplateEngine(new VelocityTemplateEngine());

        //自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<>();
                //配置是否生成supper class
                map.put("hasSuperController", superControllerClass != null);
                map.put("hasSuperService", superServiceClass != null);
                map.put("hasSuperServiceImpl", superServiceImplClass != null);
                map.put("hasSuperEntity", superEntityClass != null);
                map.put("hasSuperMapper", superMapperClass != null);
                this.setMap(map);
            }
        };
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        if (createXml) {
            String templatePath = "/templates/mapper.xml.vm";
            // 自定义配置会被优先输出
            focList.add(new FileOutConfig(templatePath) {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return projectPath + "src/main/resources/" + packageXml + "/" + tableInfo.getEntityName() + xmlSuffix + StringPool.DOT_XML;
                }
            });
        }
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 执行生成
        mpg.execute();
    }

    private void validArgument() {
        Objects.requireNonNull(dbUrl, "please set dbUrl");
        Objects.requireNonNull(dbUsername, "please set dbUsername");
        Objects.requireNonNull(dbPassword, "please set dbPassword");
        Objects.requireNonNull(tableInclude, "please set tableInclude");
        Objects.requireNonNull(packageParent, "please set packageParent");
        if (createController) {
            Objects.requireNonNull(packageController, "please set packageController");
        }
        if (createService) {
            Objects.requireNonNull(packageService, "please set packageService");
        }
        if (createServiceImpl) {
            Objects.requireNonNull(packageServiceImpl, "please set packageServiceImpl");
        }
        if (createEntity) {
            Objects.requireNonNull(packageEntity, "please set packageEntity");
        }
        if (createMapper) {
            Objects.requireNonNull(packageMapper, "please set packageMapper");
        }
        if (createXml) {
            Objects.requireNonNull(packageXml, "please set packageXml");
        }
    }

    public GenerateCode setModulePath(String modulePath) {
        this.modulePath = modulePath;
        return this;
    }

    public GenerateCode setFileOverride(boolean fileOverride) {
        this.fileOverride = fileOverride;
        return this;
    }

    public GenerateCode setAuthor(String author) {
        this.author = author;
        return this;
    }

    public GenerateCode setBaseResultMap(boolean baseResultMap) {
        this.baseResultMap = baseResultMap;
        return this;
    }

    public GenerateCode setBaseColumnList(boolean baseColumnList) {
        this.baseColumnList = baseColumnList;
        return this;
    }

    public GenerateCode setEnableCache(boolean enableCache) {
        this.enableCache = enableCache;
        return this;
    }

    public GenerateCode setSwagger2(boolean swagger2) {
        this.swagger2 = swagger2;
        return this;
    }

    public GenerateCode setDateType(DateType dateType) {
        this.dateType = dateType;
        return this;
    }

    public GenerateCode setControllerSuffix(String controllerSuffix) {
        this.controllerSuffix = controllerSuffix;
        return this;
    }

    public GenerateCode setServiceSuffix(String serviceSuffix) {
        this.serviceSuffix = serviceSuffix;
        return this;
    }

    public GenerateCode setServiceImplSuffix(String serviceImplSuffix) {
        this.serviceImplSuffix = serviceImplSuffix;
        return this;
    }

    public GenerateCode setXmlSuffix(String xmlSuffix) {
        this.xmlSuffix = xmlSuffix;
        return this;
    }

    public GenerateCode setMapperSuffix(String mapperSuffix) {
        this.mapperSuffix = mapperSuffix;
        return this;
    }

    public GenerateCode setDbType(DbType dbType) {
        this.dbType = dbType;
        return this;
    }

    public GenerateCode setDbDriverName(String dbDriverName) {
        this.dbDriverName = dbDriverName;
        return this;
    }

    public GenerateCode setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
        return this;
    }

    public GenerateCode setDbUsername(String dbUsername) {
        this.dbUsername = dbUsername;
        return this;
    }

    public GenerateCode setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
        return this;
    }

    public GenerateCode setTypeConvert(ITypeConvert typeConvert) {
        this.typeConvert = typeConvert;
        return this;
    }

    public GenerateCode setTablePrefix(String... tablePrefix) {
        this.tablePrefix = tablePrefix;
        return this;
    }

    public GenerateCode setFieldPrefix(String... fieldPrefix) {
        this.fieldPrefix = fieldPrefix;
        return this;
    }

    public GenerateCode setTableNaming(NamingStrategy tableNaming) {
        this.tableNaming = tableNaming;
        return this;
    }

    public GenerateCode setColumnNaming(NamingStrategy columnNaming) {
        this.columnNaming = columnNaming;
        return this;
    }

    public GenerateCode setTableInclude(String... tableInclude) {
        this.tableInclude = tableInclude;
        return this;
    }

    public GenerateCode setTableExclude(String... tableExclude) {
        this.tableExclude = tableExclude;
        return this;
    }

    public GenerateCode setRestControllerStyle(boolean restControllerStyle) {
        this.restControllerStyle = restControllerStyle;
        return this;
    }

    public GenerateCode setSuperControllerClass(String superControllerClass) {
        this.superControllerClass = superControllerClass;
        return this;
    }

    public GenerateCode setSuperServiceClass(String superServiceClass) {
        this.superServiceClass = superServiceClass;
        return this;
    }

    public GenerateCode setSuperServiceImplClass(String superServiceImplClass) {
        this.superServiceImplClass = superServiceImplClass;
        return this;
    }

    public GenerateCode setSuperEntityClass(String superEntityClass) {
        this.superEntityClass = superEntityClass;
        return this;
    }

    public GenerateCode setSuperMapperClass(String superMapperClass) {
        this.superMapperClass = superMapperClass;
        return this;
    }

    public GenerateCode setChainModel(boolean chainModel) {
        this.chainModel = chainModel;
        return this;
    }

    public GenerateCode setEntityLombokModel(boolean entityLombokModel) {
        this.entityLombokModel = entityLombokModel;
        return this;
    }

    public GenerateCode setPackageParent(String packageParent) {
        this.packageParent = packageParent;
        return this;
    }

    public GenerateCode setPackageController(String packageController) {
        this.packageController = packageController;
        return this;
    }

    public GenerateCode setPackageService(String packageService) {
        this.packageService = packageService;
        return this;
    }

    public GenerateCode setPackageServiceImpl(String packageServiceImpl) {
        this.packageServiceImpl = packageServiceImpl;
        return this;
    }

    public GenerateCode setPackageEntity(String packageEntity) {
        this.packageEntity = packageEntity;
        return this;
    }

    public GenerateCode setPackageMapper(String packageMapper) {
        this.packageMapper = packageMapper;
        return this;
    }

    public GenerateCode setPackageXml(String packageXml) {
        this.packageXml = packageXml;
        return this;
    }

    public GenerateCode setCreateController(boolean createController) {
        this.createController = createController;
        return this;
    }

    public GenerateCode setCreateService(boolean createService) {
        this.createService = createService;
        return this;
    }

    public GenerateCode setCreateServiceImpl(boolean createServiceImpl) {
        this.createServiceImpl = createServiceImpl;
        return this;
    }

    public GenerateCode setCreateEntity(boolean createEntity) {
        this.createEntity = createEntity;
        return this;
    }

    public GenerateCode setCreateMapper(boolean createMapper) {
        this.createMapper = createMapper;
        return this;
    }

    public GenerateCode setCreateXml(boolean createXml) {
        this.createXml = createXml;
        return this;
    }
}
